# -*- coding: utf-8 -*-

from error import LTSError

class State:
	"""
		Esta clase representa un estado de un LTS.
		
		Las instancias de State() sirven para repcentar estados en un lts, 
		en un diagnoser y en una twin-plant. La forma de diferenciar a cual de 
		estos tipos de sistemas pertence un estado es de acuerdo a la 
		informacion que este contiene. Por ejemplo, un estado de un lts deberia
		tener con valor 'None' los atributos 'fault_info', 'left_state' y 
		'right_statet'; un estado de un diagnoser deberia tener con valor 'Noen'
		los atributos 'left_state' y 'right_statet'; y un estado de una 
		twin-plant deberia tener todos estos atributos con algun valor. Otra 
		forma es mirando la reprecentacion en un string del estado, ya que para 
		cada uno de los tres tipos de sistemas los estados tiene una 
		reprecentacion diferente como cadena de caracteres.
				
		Si bien no es necesario tener metodos para acceder o modificar los 
		atributos publicos de una clase, en este caso necesitamos que para 
		ciertos atributos tales como 'name', 'init', y 'fault_info' estos 
		metodos existan, pues hay funciones que necesitan como argumento un 
		metodo que medifique estos atributos.
	"""
	
	### Atributos.
	
	# Nombre del estado.
	name = ''
	# Dice si es el estado inicial o no.
	init = False
	# Informacion de fallas
	fault_info = None
	# Informacion de estados (Izquierdo,Derecho).
	left_state = None
	right_state = None
	# Dice si fue visitado o no.
	visited = False
	
	def __init__ (self, name='', init=False):
		# Relacion de vecino. Es un conjunto de tuplas=(event,state).
		self._neighbors = set()
		self.name = name
		self.init = init
	
	def __eq__ (self, y):
		result = False
		if y == None:
			result = False
		else:
			result = (self.fault_info == y.fault_info) and \
					 (self.left_state == y.left_state) and \
					 (self.right_state==y.right_state) and (self.name==y.name)
		return result
	
	def __str__ (self):
		result = self.name
		if self.left_state!=None and self.right_state!=None:
			result += '#L('
			if self.left_state.fault_info:
				for f in self.left_state.fault_info:
					if result.endswith('('):
						result += f
					else:
						result += ','+f
			result += ')#R('
			if self.right_state.fault_info:
				for f in self.right_state.fault_info:
					if result.endswith('('):
						result += f
					else:
						result += ','+f
			result += ')'
		elif self.fault_info != None:
			result += '#('
			for f in self.fault_info:
				if result.endswith('('):
					result += f
				else:
					result += ','+f
			result += ')'
		else:
			pass
		return result
	
	def __hash__ (self):
		return self.name.__hash__()
	
	### Metodos para la relacion de vecino.
	
	def is_neighbor (self, neighbor, event=None):
		"""
			Descripcion:
				Dice si un estado esta relacionado con 'self', es decir, dice si 
				un estado pertence a la relacion de vecino de 'self' o no.
				Mas formalmente: self.is_neighbor(n,e)=True <==> Exist (n',e') 
				in self._neighbors | n=n' and (e=e' or e=None).
				Si el argumento 'event' es especificado, nos dice si un estado 
				esta relacionado con 'self' con evento 'event'.
			Argumentos:
				neighbor	-	Una instancia de la clase State() de la cual se
								quiere saber si esta relacionada con 'self'.
				event		-	Opcional. Es una cadena de caracteres que 
								representa un estado. Si este arguemtno se 
								especifica entonces se busca si existe tal 
								relacion pero con este evento.
			Retorno:
				True	-	Si 'neighbor' esta relacionado con 'self'.
				False	-	En caso contrario.
		"""
		xs = [t[1] for t in self._neighbors \
				if t[1]==neighbor and (not event or t[0]==event)]
		return neighbor in xs
	
	def add_neighbor (self, event, neighbor):
		"""
			Descripcion:
				Agrega un estado a la relacion de vecinos.
			Argumentos:
				event		-	Es una cadena de carateres que representa el 
								evento con el cual se agrega la transicion.
				neighbor	-	Una instancia de la clase State() la cual se
								quiere agregar a la relacion de vecino.
			Retorno:
				Ninguno.
			
		"""
		self._neighbors.add((event,neighbor))
	
	def remove_neighbor (self, event, neighbor):
		"""
			Descripcion:
				Elimina un estado a la relacion de vecinos.
			Argumentos:
				event		-	Es una cadena de carateres que representa el 
								evento de la transicion a eliminar.
				neighbor	-	Una instancia de la clase State() la cual se
								quiere eliminar a la relacion de vecino.
			Retorno:
				Ninguno.
		"""
		self._neighbors.remove((event,neighbor))
	
	def get_neighbor_set (self):
		"""
			Descripcion:
				Devuelve el conjunto de relacion de vecinos.
				NOTA: Devuelve una referencia al conjunto, no una copia de el.
			Argumentos:
				Nunguno.
			Retorno:
				Una instancia de la clase Set() que es el conjunto de vecinos
				de 'self'.
		"""
		return self._neighbors
	
	def reset_neighbor_set (self):
		"""
			Descripcion:
				Elimina todos los elementos de la relacion de vecino.
			Argumentos:
				Ninguno.
			Retorno:
				Ninguno.
		"""
		self._neighbors = set()
	
	### Metodos para el nombre.
	
	def set_name (self, name):
		"""
			Descripcion:
				Establece el nombre de un estado.
			Argumentos:
				name	-	Una cadena de caracteres que reprecente el nombre 
							del estado.
			Retorno:
				Ninguno.
		"""
		self.name = name
	
	def get_name (self):
		"""
			Descripcion:
				Devuelve el nombre del estado.
			Argumentos:
				Ninguno.
			Retorno:
				Una cadena de caracteres que representa el nombre del estado.
		"""
		return self.name
	
	### Metodos para el indicador de estado incial.
	
	def set_init_state (self, init):
		"""
			Descripcion:
				Establece si es o no el estado inicial.
			Argumentos:
				init	-	Un booleano que indica si el estado es inicial o no.
			Retorno:
				Ninguno.
		"""
		self.init = init
	
	def is_init_state (self):
		"""
			Descripcion:
				Dice si el estado es un estado inicial o no.
			Argumentos:
				Ninguno.
			Retorno:
				True	-	Si el estado es inicial.
				False	-	Si no lo es.
		"""
		return self.init
	
	### Metodos para la informacion de fallas.
	
	def add_fault_info (self, fault):
		"""
			Descripcion:
				Agregala una falla al conjunto de fallas del estado.
			Argumentos:
				fault	-	Una cadena de caracteres que representa una falla.
			Retorno:
				Ninguno.
		"""
		if self.fault_info == None:
			self.fault_info = set()
		self.fault_info.add(fault)
	
	def get_fault_info (self):
		"""
			Descripcion:
				Devuelve el conjunto de fallas del estado
				NOTA: Devuelve una referencia al conjunto, no una copia de el.
			Argumentos:
				Nunguno.
			Retorno:
				Una instancia de la clase Set() que es el conjunto de fallas.
		"""
		if self.fault_info != None:
			return self.fault_info
		else:
			return set()
